package com.tancire.sources.utils;

import com.alibaba.fastjson2.JSONObject;
import com.tancire.common.constant.SourceConst;
import com.tancire.common.core.redis.RedisCache;
import com.tancire.common.exception.ResourceException;
import com.tancire.common.utils.ServletUtils;
import com.tancire.common.utils.file.FileUtils;
import com.tancire.common.utils.ip.IpUtils;
import com.tancire.common.utils.uuid.IdUtils;
import com.tancire.file.config.MinioConfig;
import com.tancire.sources.domain.SourceAuth;
import com.tancire.sources.domain.WriteFile;
import io.minio.GetObjectArgs;
import io.minio.MinioClient;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;

import static com.tancire.common.constant.CacheConstants.SOURCE_AUTH_KEY;

/**
 * 写文件流
 *
 * @author chenchao
 * @version 1.0
 */
@Slf4j
@Component
public class WriteFileUtils {

    @Resource
    private RedisCache redisCache;

    @Resource
    private MinioConfig minioConfig;

    @Resource
    private MinioClient client;

    /**
     * 写预览流
     *
     * @param file
     * @param req
     * @param res
     */
    public void view(WriteFile file, HttpServletRequest req, HttpServletResponse res) {
        //第一步：先获取分段开始和结束位置
        long start = 0;     //开始的下标
        long toLength = 0;  //结束的下标
        long contentLength = file.getFileSize();  //文件总长度
        long actualLength = 0;               //实际传输长度

        String range = req.getHeader("Range"); //从请求中拿到数据。
        if (range != null) {
            char[] chars = range.replace("bytes=", "").toCharArray();
            if (FileUtils.isNumber(chars)) {
                if (chars[chars.length - 1] == '-') {
                    start = FileUtils.rNum(0, chars.length - 1, chars);
                    toLength = contentLength;
                    actualLength = contentLength - start;
                } else {
                    start = FileUtils.rNum(0, range.indexOf("-"), chars);
                    toLength = FileUtils.rNum(range.indexOf("-") + 1, chars.length, chars);
                    actualLength = toLength - start;
                }
            }
        } else {
            toLength = contentLength;
            actualLength = contentLength;
        }

        String bucketName = minioConfig.getPrvBucketName();
        // 获取对象范围
        GetObjectArgs objectArgs = GetObjectArgs.builder()
                .bucket(bucketName)
                .object(file.getFilePath())
                // 例如，从1024字节开始读取
                .offset(start)
                // 读取4096字节长度的文件片段
                .length(actualLength)
                .build();
        try (InputStream is = client.getObject(objectArgs);
             OutputStream out = res.getOutputStream();
             BufferedInputStream bif = new BufferedInputStream(is)) {

            //第二步：响应头 Content-Range 的内容
            String rangeText = new String(new StringBuffer("bytes ")
                    .append(start)
                    .append("-")
                    .append(toLength - 1)
                    .append("/")
                    .append(contentLength)
            );
            //第三步：设置响应头
            res.setHeader("Accept-Ranges", "bytes");    //必设，表示支持断点传输
            res.setStatus(javax.servlet.http.HttpServletResponse.SC_PARTIAL_CONTENT);  //根据需求实现
            res.setHeader("Content-Length", actualLength + ""); //
            res.setHeader("Content-Range", rangeText);

            String fileType = file.getFileType();
            res.setHeader("Content-Type", FileUtils.getContentType(fileType));

            //第四步：根据 range 从不同的地方开始读取流

            byte[] bytes = new byte[1024];
            int n = 0;
            while ((n = bif.read(bytes)) != -1) {
                out.write(bytes, 0, n);
            }
            out.flush();
        } catch (Exception e) {
            log.info("写流关闭异常：{}", e.getMessage());
        }
    }

    /**
     * 写文件流
     *
     * @param file
     * @param res
     */
    public void write(WriteFile file, HttpServletResponse res) {
        // 设置文件返回类型
        String fileName = null;
        try {
            fileName = URLEncoder.encode(file.getFileName() + "." + file.getFileType(), StandardCharsets.UTF_8.toString());
        } catch (UnsupportedEncodingException e) {
            throw new ResourceException("资源名处理异常");
        }
        String headerValue = String.format("attachment; filename=\"%s\"", fileName);
        res.setHeader(HttpHeaders.CONTENT_DISPOSITION, headerValue);
        res.setHeader(HttpHeaders.CONTENT_TYPE, "application/octet-stream");
        res.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(file.getFileSize()));

        try (InputStream is = client.getObject(GetObjectArgs.builder().bucket(minioConfig.getPrvBucketName()).object(file.getFilePath()).build());
             OutputStream os = res.getOutputStream()) {
            // 获取文件流

            byte[] b = new byte[1024];
            int len;
            while ((len = is.read(b)) > 0) {
                os.write(b, 0, len);
            }
            os.flush();
        } catch (Exception e) {
            // e.printStackTrace();
        }
    }

    /**
     * 校验刷新
     *
     * @param sourceAuth
     * @param code
     * @return
     */
    public boolean checkAndRefresh(SourceAuth sourceAuth, String code) {
        try {
            String expire = sourceAuth.getExpire();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            Date parse = sdf.parse(expire);
            long time = new Date(System.currentTimeMillis()).getTime();
            // 当前时间大于超时时间
            if (time > parse.getTime()) {
                return false;
            }
            // 刷新缓存时间
            time = time + SourceConst.DEFAULT_EXPIRE_TIME;
            Date expireTime = new Date(time);
            String format = sdf.format(expireTime);
            sourceAuth.setExpire(format);
            // 刷新缓存
            redisCache.setCacheObject(SOURCE_AUTH_KEY + code, JSONObject.toJSONString(sourceAuth), 24, TimeUnit.HOURS);
            return true;
        } catch (Exception e) {
            // 校验失败不处理
        }
        return false;
    }

    /**
     * 鉴权校验
     *
     * @param code
     * @return
     */
    public SourceAuth checkAuth(String code) {
        Object cacheObject = redisCache.getCacheObject(SOURCE_AUTH_KEY + code);
        if (null == cacheObject) {
            throw new ResourceException("当前资源没有权限");
        }
        log.info("获得缓存信息：{}", cacheObject);
        SourceAuth sourceAuth = JSONObject.parseObject((String) cacheObject, SourceAuth.class);
        if (!this.checkAndRefresh(sourceAuth, code)) {
            throw new ResourceException("资源连接超时，请刷新页面重试");
        }
        return sourceAuth;
    }

    /**
     * 生成临时授权码
     *
     * @param id
     * @return
     */
    public String generateCode(Long id) {
        String code = IdUtils.simpleUUID();
        SourceAuth sourceAuth = new SourceAuth();
        sourceAuth.setId(id);
        String ipAddr = IpUtils.getIpAddr(ServletUtils.getRequest());
        sourceAuth.setIp(ipAddr);
        Date date = new Date(System.currentTimeMillis() + SourceConst.DEFAULT_EXPIRE_TIME);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        sourceAuth.setExpire(sdf.format(date));
        redisCache.setCacheObject(SOURCE_AUTH_KEY + code, JSONObject.toJSONString(sourceAuth), 24, TimeUnit.HOURS);
        return code;
    }
}
